/*!
\copyright  Copyright (c) 2021 - 2022 Qualcomm Technologies International, Ltd.\n
            All Rights Reserved.\n
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Volume observer that syncs between AVRCP and USB
*/

#ifdef INCLUDE_SOURCE_APP_BREDR_AUDIO
#include "usb_dongle_logging.h"
#include "usb_dongle_volume_observer.h"
#include "audio_sources.h"
#include <usb_device.h>
#include <usb_audio.h>
#include <volume_utils.h>
#include <volume_service.h>
#include <volume_messages.h>
#include <telephony_messages.h>
#include <avrcp.h>
#include <aghfp.h>
#include <av.h>
#include <aghfp_profile.h>

#define USB_DONGLE_VO_USB_VOLUME_CHANGE_DELAY_MS    20  /*!< delay between sending USB Volume inc/dec commands */
#define USB_DONGLE_VO_AVRCP_VOLUME_CHANGE_DELAY_MS  200 /*!< delay between sending AVRCP Volume updates commands */

/* if the USB host becomes unresponsive, we need to stop sending volume messages */
#define USB_VOLUME_CHANGE_COUNT_MAX 20

/* Burst sent to classify Host OS */
#define BURST_SIZE 4

static void usbDongle_NotifyAudioRoutingChange(audio_source_t source, source_routing_change_t change);
static void usbDongle_NotifyVolumeChange(audio_source_t source, event_origin_t origin, volume_t volume);
static void usbDongle_HandleUpdateUsbVolume(void);
static void usbDongle_NotifyVoiceVolumeChange(voice_source_t source, event_origin_t origin, volume_t volume);
static void usbDongle_NotifyVoiceMuteChange(voice_source_t source, event_origin_t origin, bool mute_state);
static void usbDongle_SendHidVolumeMessage(void);
static void usbDongle_UpdateVolumeChangeDirection(volume_t usb_volume, int16 new_usb_volume);

enum vo_internal_message_ids
{
    VO_INTERNAL_UPDATE_USB_VOLUME,      /*!< Message used to create a delay between each USB volume inc/dec */
    VO_INTERNAL_UPDATE_AVRCP_VOLUME     /*!< Message used to create a delay between each AVRCP absolute volume */
};

typedef enum vo_internal_state
{
    VO_INTERNAL_STATE_IDLE,                 /*!< No volume changes in this state */
    VO_INTERNAL_STATE_USB_VOLUME_SYNC,      /*!< Syncing the USB volume */
    VO_INTERNAL_STATE_SINK_VOLUME_SYNC      /*!< Syncing the Sink volume */
} vo_internal_state_t;

typedef enum host_os_type
{
    HOST_OS_TYPE_WINDOWS_MAC,        /*!< Host OS is Windows or MacOS */
    HOST_OS_TYPE_LINUX_ANDROID,      /*!< Host OS is Linux or Android */
    HOST_OS_TYPE_UNIDENTIFIED        /*!< Host OS has not been identified */
} host_os_type_t;

typedef struct {
    TaskData task;

    bool usb_connected; 		/*!< USB is connected */
    bool avrcp_connected;		/*!< AVRCP is connected */
    bool aghfp_connected;		/*!< AGHFP is connected */

    avInstanceTaskData *av_instance;
    aghfpInstanceTaskData *aghfp_instance;

    /* data for handling USB volume updates */
    struct {
        uint8 count;            /*!< volume changes sent without a volume update from the USB host */
        int16 target;           /*!< target volume we are aiming for */
        bool up;                /*!< TRUE = increase volume, FALSE = decrement volume */
    } usb_volume;

    /* data for handling AVRCP volume updates */
    struct {
        uint8 target;           /*!< value to set on AVRCP */
    } avrcp_volume;

    vo_internal_state_t state;
    bool mic_muted;

    bool hold_active; /*!< TRUE = Call is on HOLD, FALSE = Call is NOT on HOLD */
    bool pre_hold_mute_state; /*!< State of mute before the call went on hold so that state can be reverted to */
    
    volume_t old_volume; /*!< Previous USB volume */
    host_os_type_t host_os; /*!< Holds the Host OS type */

} usb_dongle_volume_observer_data;

static usb_dongle_volume_observer_data volume_observer_data;
#define GetTaskData()   (&volume_observer_data)
#define GetTask()       (&(GetTaskData()->task))

/* The volume range of AVRCP */
const volume_config_t avrcp_config = {
    .range = {
        .max = 127,
        .min = 0
    },
    .number_of_steps = 127
};

/* The volume range of USB */
const volume_config_t usb_config = {
    .range = {
        .max = 100,
        .min = 0
    },
    .number_of_steps = 100
};

/* The volume range that the Host sends to us */
const volume_config_t host_out_config = {
    .range = {
        .max = USB_AUDIO_VOLUME_MAX_STEPS,
        .min = USB_AUDIO_VOLUME_MIN_STEPS
    },
    .number_of_steps = USB_AUDIO_VOLUME_NUM_STEPS
};

static const audio_source_observer_interface_t usb_dongle_observer_interface =
{
    .OnVolumeChange = usbDongle_NotifyVolumeChange,
    .OnAudioRoutingChange = usbDongle_NotifyAudioRoutingChange
};

static const voice_source_observer_interface_t usb_dongle_voice_observer_interface =
{
    .OnVolumeChange = usbDongle_NotifyVoiceVolumeChange,
    .OnMuteChange = usbDongle_NotifyVoiceMuteChange
};

typedef enum
{
    request_type_host_to_sink,
    request_type_sink_to_host
} request_type_t;


/* send the AVRCP volume update */
static void usbDongle_SendAvrcpVolumeUpdate(void)
{
    DEBUG_LOG_V_VERBOSE("usbDongle_SendAvrcpVolumeUpdate, volume %d", GetTaskData()->avrcp_volume.target);
    appAvrcpSetAbsoluteVolumeRequest(GetTaskData()->av_instance, GetTaskData()->avrcp_volume.target);
}

/*
 * Throttle the rate at which we send AVRCP absolute volume updates
 * This is done to avoid a memory exhausation situation if the PC host sends us too many volume update messages
 */
static void usbDongle_QueueAvrcpVolumeUpdate(uint8  volume)
{
    int32 dummy;

    DEBUG_LOG_FN_ENTRY("usbDongle_QueueAvrcpVolumeUpdate");

    GetTaskData()->avrcp_volume.target = volume;
    if ( !MessagePendingFirst(GetTask(), VO_INTERNAL_UPDATE_AVRCP_VOLUME, &dummy) )
    {
        DEBUG_LOG_V_VERBOSE("usbDongle_QueueAvrcpVolumeUpdate, queuing AVRCP volume update");
        MessageSendLater(GetTask(), VO_INTERNAL_UPDATE_AVRCP_VOLUME, NULL, USB_DONGLE_VO_AVRCP_VOLUME_CHANGE_DELAY_MS);
    }
    else
    {
        DEBUG_LOG_V_VERBOSE("usbDongle_QueueAvrcpVolumeUpdate, not queuing, volume update already pending");
    }
}

static void usbDongle_UpdateSinkVolume(volume_t volume)
{
    if (GetTaskData()->aghfp_connected)
    {
        /* Call the volume service to update AGHFP Volume */
        VolumeService_SetVoiceSourceVolume(voice_source_hfp_1, event_origin_local, volume);

    } else
    {
       DEBUG_LOG("usbDongle_UpdateSinkVolume, no AGHFP connection");
    }

    if (GetTaskData()->avrcp_connected)
    {
        volume_t old_avrcp_volume = AudioSources_GetVolume(audio_source_a2dp_1);
        int16 new_avrcp_volume;
        new_avrcp_volume = VolumeUtils_ConvertToVolumeConfig(volume, avrcp_config);
        DEBUG_LOG("UsbDongle_UpdateAvrcpVolume, USB %d, old AVRCP %d, new AVRCP %d", volume.value, old_avrcp_volume.value, new_avrcp_volume);
        usbDongle_QueueAvrcpVolumeUpdate(new_avrcp_volume);
    } else
    {
        DEBUG_LOG("usbDongle_UpdateSinkVolume, no AVRCP connection");
    }
}

/*
 * Different strategies to update USB volume target depending on the host OS.
 */
static void usbDongle_UpdateUsbVolumeTarget(volume_t avrcp_volume)
{
    volume_t usb_volume = AudioSources_GetVolume(audio_source_usb);
    int16 new_usb_volume = VolumeUtils_ConvertToVolumeConfig(avrcp_volume, host_out_config);
    volume_t MidVolume = avrcp_volume;

    DEBUG_LOG("usbDongle_UpdateUsbVolumeTarget, AVRCP %d, old USB %d, new USB %d", avrcp_volume.value,usb_volume.value,new_usb_volume );
    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);

    switch(GetTaskData()->host_os)
    {
        /* Current USB volume compared to target USB volume. */
        case HOST_OS_TYPE_WINDOWS_MAC:
            usbDongle_UpdateVolumeChangeDirection(usb_volume, new_usb_volume);
            break;

        /* Previous target USB volume compared to new target USB volume.
         * Because we do not know current USB volume. */
        case HOST_OS_TYPE_LINUX_ANDROID:
            usbDongle_UpdateVolumeChangeDirection(GetTaskData()->old_volume, new_usb_volume);
            GetTaskData()->old_volume.value = new_usb_volume;
            break;

        /* For the first volume request, current USB volume is compared to target USB volume.
         * And the AVRCP volume is set.*/
        case HOST_OS_TYPE_UNIDENTIFIED:
            /* Mapping current host volume to the middle of the AVRCP volume range by setting the AVRCP volume to middle of its range. */
            MidVolume.value = (avrcp_config.range.max - avrcp_config.range.min) / 2;
            AudioSources_SetVolume(audio_source_a2dp_1,MidVolume);

            usbDongle_UpdateVolumeChangeDirection(usb_volume, new_usb_volume);
            break;

        default:
            break;
    }
}

/*
 * Find if we need to increment or decrement the USB volume
 */
static void usbDongle_UpdateVolumeChangeDirection(volume_t usb_volume, int16 new_usb_volume)
{
    /* USB volume is already at the target, no change required */
    if (new_usb_volume == usb_volume.value)
    {
        DEBUG_LOG_VERBOSE("usbDongle_UpdateUsbVolumeChangeDirection, no change" );
        GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
    }
    else    /* calculate the change, up or down */
    {
        GetTaskData()->usb_volume.target = new_usb_volume;
        if (new_usb_volume > usb_volume.value)
        {
            DEBUG_LOG_VERBOSE("usbDongle_UpdateUsbVolumeChangeDirection, volume up" );
            GetTaskData()->usb_volume.up = TRUE;
        }
        else
        {
            DEBUG_LOG_VERBOSE("usbDongle_UpdateUsbVolumeChangeDirection, volume down" );
            GetTaskData()->usb_volume.up = FALSE;
        }
        /* send HID volume messages */
        usbDongle_HandleUpdateUsbVolume();
    }
}

static void usbDongle_StateUsbVolumeSync(request_type_t request_type, volume_t volume)
{
    switch(request_type)
    {
        /* Host has sent us a USB volume update, check if we've met the target */
        case request_type_host_to_sink:
            GetTaskData()->usb_volume.count = 0;
            GetTaskData()->host_os = HOST_OS_TYPE_WINDOWS_MAC;
            DEBUG_LOG_INFO("usbDongle_StateUsbVolumeSync, likely host OS: Windows or Mac");
            if (GetTaskData()->usb_volume.up )
            {
                if ( volume.value >= GetTaskData()->usb_volume.target )
                {
                    DEBUG_LOG_VERBOSE("usbDongle_StateUsbVolumeSync, volume target met");
                    GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
                    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
                }
            } else
            {
                if ( volume.value <= GetTaskData()->usb_volume.target )
                {
                    DEBUG_LOG_VERBOSE("usbDongle_StateUsbVolumeSync, volume target met");
                    GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
                    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
                }
            }
            break;

        /* Sink is requesting a volume update while we are syncing the USB volume, update target */
        case request_type_sink_to_host:
            usbDongle_UpdateUsbVolumeTarget(volume);
            break;

        default:
            DEBUG_LOG("usbDongle_StateUsbVolumeSync, invalid source");
            break;
    }
}

static void usbDongle_StateSinkVolumeSync(request_type_t request_type, volume_t volume)
{
    switch(request_type)
    {
        /* Host volume changed, update the Sink volume */
        case request_type_host_to_sink:
            usbDongle_UpdateSinkVolume(volume);
            break;

        /* Sink volume changed, update the USB volume, change state */
        case request_type_sink_to_host:
            GetTaskData()->state = VO_INTERNAL_STATE_USB_VOLUME_SYNC;
            GetTaskData()->usb_volume.count = 0;
            usbDongle_UpdateUsbVolumeTarget(volume);
            break;

        default:
            DEBUG_LOG("usbDongle_StateSinkVolumeSync, invalid source");
            break;
    }
}

static void usbDongle_NotifyAudioRoutingChange(audio_source_t source, source_routing_change_t change)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_NotifyAudioRoutingChange, enum:audio_source_t:%d, enum:source_routing_change_t:%d", source, change);
}

static void usbDongle_HandleNotifyVolumeChange(request_type_t request_type, volume_t volume)
{
    switch(GetTaskData()->state)
    {
        case VO_INTERNAL_STATE_SINK_VOLUME_SYNC:
            usbDongle_StateSinkVolumeSync(request_type, volume);
            break;

        case VO_INTERNAL_STATE_USB_VOLUME_SYNC:
            usbDongle_StateUsbVolumeSync(request_type, volume);
            break;

        case VO_INTERNAL_STATE_IDLE:
            DEBUG_LOG("UsbDongle_NotifyVolumeChange, IDLE, no change");
            break;

        default:
            break;
    }
}

static void usbDongle_NotifyVolumeChange(audio_source_t source, event_origin_t origin, volume_t volume)
{
    DEBUG_LOG_FN_ENTRY("UsbDongle_NotifyVolumeChange, enum:audio_source_t:%d, enum:event_origin_t:%d, volume %d", source, origin, volume.value);


    if ( origin == event_origin_external )
    {
        usbDongle_HandleNotifyVolumeChange(source == audio_source_usb ? request_type_host_to_sink : request_type_sink_to_host, volume);
    } else
    {
        DEBUG_LOG("usbDongle_NotifyVolumeChange, ignoring origin enum:event_origin_t:%d", origin);
    }
}

static void usbDongle_NotifyVoiceVolumeChange(voice_source_t source, event_origin_t origin, volume_t volume)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_NotifyVoiceVolumeChange, enum:voice_source_t:%d, enum:event_origin_t:%d, volume %d", source, origin, volume.value);

    if ( origin == event_origin_external )
    {
        usbDongle_HandleNotifyVolumeChange(source == voice_source_usb ? request_type_host_to_sink : request_type_sink_to_host, volume);
    } else
    {
        DEBUG_LOG("usbDongle_NotifyVoiceVolumeChange, ignoring origin: enum:event_origin_t:%d", origin);
    }

}

static void usbDongle_NotifyVoiceMuteChange(voice_source_t source, event_origin_t origin, bool mute_state)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_NotifyVoiceMuteChange, enum:voice_source_t:%d, enum:event_origin_t:%d, mute %d", source, origin, mute_state);

}

/*! \brief Send HID Volume Up/Down message depending on host OS. */
static void usbDongle_HandleUpdateUsbVolume(void)
{
    switch(GetTaskData()->host_os)
    {
        /* Send HID message until maximum count or target volume has been met. */
        case HOST_OS_TYPE_WINDOWS_MAC:
            if ( GetTaskData()->usb_volume.count < USB_VOLUME_CHANGE_COUNT_MAX )
            {
                GetTaskData()->usb_volume.count++;
                DEBUG_LOG_V_VERBOSE("usbDongle_HandleUpdateUsbVolume, usb_volume.change_count %d", GetTaskData()->usb_volume.count );

                usbDongle_SendHidVolumeMessage();

                /* reschedule */
                MessageSendLater(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME, NULL, USB_DONGLE_VO_USB_VOLUME_CHANGE_DELAY_MS );
            } else  /* USB host has become unresponsive */
            {
                DEBUG_LOG_ERROR("usbDongle_HandleUpdateUsbVolume, USB host has become unresponsive, stop sending USB HID volume messages.");
                GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
            }
            break;

        /* Send single HID message per volume change request. */
        case HOST_OS_TYPE_LINUX_ANDROID:
            usbDongle_SendHidVolumeMessage();
            break;

        /* Start a burst of HID messages to identify host OS. */
        case HOST_OS_TYPE_UNIDENTIFIED:
            if ( GetTaskData()->usb_volume.count < BURST_SIZE)
            {
                GetTaskData()->usb_volume.count++;
                DEBUG_LOG_V_VERBOSE("usbDongle_HandleUpdateUsbVolume, usb_volume.change_count %d",GetTaskData()->usb_volume.count);
                usbDongle_SendHidVolumeMessage();
                MessageSendLater(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME, NULL, USB_DONGLE_VO_USB_VOLUME_CHANGE_DELAY_MS );
            } else
            {
                GetTaskData()->host_os = HOST_OS_TYPE_LINUX_ANDROID;
                DEBUG_LOG_INFO("usbDongle_HandleUpdateUsbVolume, likely host OS: Linux or Android");
                GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
            }
            break;

        default:
            break;
    }
}

/*
 * Send a HID Volume Up/Down message to the host
 */
static void usbDongle_SendHidVolumeMessage(void)
{
    if ( GetTaskData()->usb_volume.up )
    {
        DEBUG_LOG_VERBOSE("usbDongle_SendHidVolumeMessage, up");
        Volume_SendAudioSourceVolumeIncrementRequest(audio_source_usb, event_origin_local);
    }
    else
    {
        DEBUG_LOG_VERBOSE("usbDongle_SendHidVolumeMessage, down");
        Volume_SendAudioSourceVolumeDecrementRequest(audio_source_usb, event_origin_local);
    }
}

static void usbDongle_HandleUsbAudioConnectedInd(void)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleUsbAudioConnectedInd");
    GetTaskData()->usb_connected = TRUE;
    if ( GetTaskData()->avrcp_connected || GetTaskData()->aghfp_connected )
    {
        GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
    }
}

static void usbDongle_HandleUsbAudioDisconnectedInd(void)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleUsbAudioDisconnectedInd");
    GetTaskData()->usb_connected = FALSE;
    GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
    MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_AVRCP_VOLUME);
}

static void usbDongle_HandleTelephonyMuteActive(TELEPHONY_MUTE_ACTIVE_T *mute)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleTelephonyMuteActive, source enum:voice_source_t:%d", mute->voice_source);

    /* If the microphone is already muted then ignore the message */
    if (GetTaskData()->mic_muted)
    {
        return;
    }
    
    /* If the message comes from HFP then forward to USB
       If the message comes from USB then forward to HFP
       Note: If the call was on hold we should not set the mute status */
    if (mute->voice_source == voice_source_hfp_1 && !GetTaskData()->hold_active)
    {
        VoiceSources_ToggleMicrophoneMute(voice_source_usb);
    }
    else if (mute->voice_source == voice_source_usb)
    {
        VoiceSources_ToggleMicrophoneMute(voice_source_hfp_1);
    }

    GetTaskData()->mic_muted = TRUE;
}

static void usbDongle_HandleTelephonyMuteInactive(TELEPHONY_MUTE_INACTIVE_T *mute)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleTelephonyMuteInactive, source enum:voice_source_t:%d", mute->voice_source);

    /* If the microphone is already unmuted then ignore the message */    
    if (!GetTaskData()->mic_muted)
    {
        return;
    }

    /* If the message comes from HFP then forward to USB
       If the message comes from USB then forward to HFP 
       Note: If the call was on hold we should not set the mute status */
    if (mute->voice_source == voice_source_hfp_1 && !GetTaskData()->hold_active)
    {
        VoiceSources_ToggleMicrophoneMute(voice_source_usb);
    }
    else if (mute->voice_source == voice_source_usb)
    {
        VoiceSources_ToggleMicrophoneMute(voice_source_hfp_1);
    }

    GetTaskData()->mic_muted = FALSE;
}

/* if the USB Headset had been muted, we pass it straight through to HFP */
static void usbDongle_HandleUsbHeadsetMuteActive(void)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleUsbHeadsetMuteActive");
    if (!GetTaskData()->mic_muted)
    {
        VoiceSources_ToggleMicrophoneMute(voice_source_hfp_1);
    }
    GetTaskData()->mic_muted = TRUE;
}

/* if the USB Headset had been unmuted, we pass it straight through to HFP */
static void usbDongle_HandleUsbHeadsetMuteInactive(void)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleUsbHeadsetMuteInactive");
    if (GetTaskData()->mic_muted)
    {
        VoiceSources_ToggleMicrophoneMute(voice_source_hfp_1);
    }
    GetTaskData()->mic_muted = FALSE;
}

static void usbDongle_HandleAvAvrcpConnectedInd(AV_AVRCP_CONNECT_IND_T *msg)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleAvAvrcpConnectedInd");
    /* Only one connection supported */
    if (!GetTaskData()->avrcp_connected)
    {
        GetTaskData()->av_instance = msg->av_instance;
        GetTaskData()->avrcp_connected = TRUE;
        if ( GetTaskData()->usb_connected )
        {
            GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
            usbDongle_UpdateSinkVolume(AudioSources_GetVolume(audio_source_usb));
        }
    }
}

static void usbDongle_HandleAvAvrcpDisconnectedInd(AV_AVRCP_DISCONNECT_IND_T *msg)
{
    UNUSED(msg);
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleAvAvrcpDisconnectedInd");
    if (GetTaskData()->avrcp_connected)
    {
        GetTaskData()->av_instance  = NULL;
        GetTaskData()->avrcp_connected = FALSE;
        MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
        MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_AVRCP_VOLUME);
        GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    }
}

static void usbDongle_HandleAghfpConnected(APP_AGHFP_CONNECTED_IND_T *ind)
{
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleAghfpConnected");
    /* Only one connection supported */
    if (!GetTaskData()->aghfp_connected)
    {
        GetTaskData()->aghfp_instance = ind->instance;
        GetTaskData()->aghfp_connected = TRUE;
        if ( GetTaskData()->usb_connected )
        {
            GetTaskData()->state = VO_INTERNAL_STATE_SINK_VOLUME_SYNC;
            usbDongle_UpdateSinkVolume(AudioSources_GetVolume(audio_source_usb));
        }
    }
}

static void usbDongle_HandleAghfpDisconnected(APP_AGHFP_DISCONNECTED_IND_T *ind)
{
    UNUSED(ind);
    DEBUG_LOG_FN_ENTRY("usbDongle_HandleAghfpDisconnected");
    if (GetTaskData()->aghfp_connected)
    {
        GetTaskData()->aghfp_instance = NULL;
        GetTaskData()->aghfp_connected = FALSE;
        MessageCancelFirst(GetTask(), VO_INTERNAL_UPDATE_USB_VOLUME);
        GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    }
}

static void usbDongle_HandleCallHoldActiveInactive(bool is_hold_active)
{
    /* Ignore hold state messages that do not change state */
    if (GetTaskData()->hold_active == is_hold_active)
    {
        return;
    }
    
    GetTaskData()->hold_active = is_hold_active;

    if (GetTaskData()->hold_active)
    {
        GetTaskData()->pre_hold_mute_state = GetTaskData()->mic_muted;
    }
    else
    {
        DEBUG_LOG("usbDongle_HandleAghfpDisconnected, call off hold, setting mute state %d ",
                  GetTaskData()->pre_hold_mute_state);
        VoiceSources_SetMuteState(voice_source_hfp_1, GetTaskData()->pre_hold_mute_state ? mute : unmute);
        /* The sink has changed the mute status while we were on hold.
           toggle the mute so that it reverts to pre hold state */
            
        /* Set the mic status to the old state */
        GetTaskData()->mic_muted = GetTaskData()->pre_hold_mute_state;
    }
}

static void usbDongle_HandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    UNUSED(message);

    switch(id)
    {
        case VO_INTERNAL_UPDATE_USB_VOLUME:
            usbDongle_HandleUpdateUsbVolume();
            break;

        case VO_INTERNAL_UPDATE_AVRCP_VOLUME:
            usbDongle_SendAvrcpVolumeUpdate();
            break;

        case USB_AUDIO_CONNECTED_IND:
            usbDongle_HandleUsbAudioConnectedInd();
            break;

        case USB_AUDIO_DISCONNECTED_IND:
            usbDongle_HandleUsbAudioDisconnectedInd();
            break;

        case USB_AUDIO_HEADSET_MIC_MUTE_ACTIVE:
            usbDongle_HandleUsbHeadsetMuteActive();
            break;

        case USB_AUDIO_HEADSET_MIC_MUTE_INACTIVE:
            usbDongle_HandleUsbHeadsetMuteInactive();
            break;

        case TELEPHONY_MUTE_ACTIVE:
            usbDongle_HandleTelephonyMuteActive((TELEPHONY_MUTE_ACTIVE_T*)message);
            break;

        case TELEPHONY_HOLD_ACTIVE:
                usbDongle_HandleCallHoldActiveInactive(TRUE);
            break;

        case TELEPHONY_HOLD_INACTIVE:
                usbDongle_HandleCallHoldActiveInactive(FALSE);
            break;

        case TELEPHONY_MUTE_INACTIVE:
            usbDongle_HandleTelephonyMuteInactive((TELEPHONY_MUTE_INACTIVE_T*)message);
            break;

        case APP_AGHFP_CONNECTED_IND:
            usbDongle_HandleAghfpConnected((APP_AGHFP_CONNECTED_IND_T*)message);
            break;

        case APP_AGHFP_DISCONNECTED_IND:
            usbDongle_HandleAghfpDisconnected((APP_AGHFP_DISCONNECTED_IND_T*)message);
            break;

        case AV_AVRCP_CONNECTED_IND:
            usbDongle_HandleAvAvrcpConnectedInd((AV_AVRCP_CONNECT_IND_T *) message);
        break;

        case AV_AVRCP_DISCONNECTED_IND:
            usbDongle_HandleAvAvrcpDisconnectedInd((AV_AVRCP_DISCONNECT_IND_T *) message);
        break;

        default:
            DEBUG_LOG("usbDongle_HandleMessage: unhandled %d", id);
            break;
    }
}


void UsbDongle_VolumeObserverInit(void)
{
    DEBUG_LOG_FN_ENTRY("UsbDongle_VolumeObserverInit");

    GetTaskData()->task.handler = usbDongle_HandleMessage;
    GetTaskData()->usb_connected = FALSE;
    GetTaskData()->aghfp_connected = FALSE;
    GetTaskData()->avrcp_connected = FALSE;
    GetTaskData()->av_instance = NULL;
    GetTaskData()->aghfp_instance = NULL;
    GetTaskData()->state = VO_INTERNAL_STATE_IDLE;
    GetTaskData()->mic_muted = FALSE;
    GetTaskData()->hold_active = FALSE;
    GetTaskData()->pre_hold_mute_state = FALSE;
    GetTaskData()->host_os = HOST_OS_TYPE_UNIDENTIFIED;

    /* Old volume initialised to midpoint of USB volume range */
    GetTaskData()->old_volume.config = usb_config;
    GetTaskData()->old_volume.value = (usb_config.range.max - usb_config.range.min) / 2;;
    
    AudioSources_RegisterObserver(audio_source_usb, &usb_dongle_observer_interface);
    AudioSources_RegisterObserver(audio_source_a2dp_1, &usb_dongle_observer_interface);
    VoiceSources_RegisterObserver(voice_source_hfp_1, &usb_dongle_voice_observer_interface);


    UsbAudio_ClientRegister(GetTask(), USB_AUDIO_REGISTERED_CLIENT_STATUS);
    Telephony_RegisterForMessages(GetTask());
    appAvStatusClientRegister(GetTask());
    AghfpProfile_RegisterStatusClient(GetTask());
}
#endif /* INCLUDE_SOURCE_APP_BREDR_AUDIO */
